<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Composites</title>
  <!-- https://chinabigpan.github.io/matterjs_docs_zh_cn/routes/Composites.html -->
  <style>
    .main {
      width: 500px;
      height: 500px;
    }
  </style>
</head>

<body>
  <div class="main">
  </div>
  <script src="matter.js"></script>
  <script>
    const { Engine, Render, World, Body, Bodies, Mouse, MouseConstraint, Constraint, Composites } = Matter;
    // 初始化画布和 Matter.js 引擎
    const main = document.querySelector('.main');
    const eleWidth = main.offsetWidth;
    const eleHeight = main.offsetHeight;
    console.log("main", eleHeight)
    const engine = Engine.create();
    const render = Render.create({
      element: main,
      engine: engine,
      options: {
        width: eleWidth,
        height: eleHeight,
        wireframes: false,
      }
    });
    // 创建世界边界
    const size = 500;
    let edgeOptions = {
      isStatic: true,
      render: {
        visible: false
      }
    };
    const edgeList = [
      Bodies.rectangle(eleWidth / 2, -1 * size, eleWidth, size * 2, edgeOptions),
      Bodies.rectangle(eleWidth / 2, eleHeight + size, eleWidth, size * 2, edgeOptions),
      Bodies.rectangle(-1 * size, eleWidth / 2, size * 2, eleHeight, edgeOptions),
      Bodies.rectangle(eleWidth + size, eleHeight / 2, size * 2, eleHeight, edgeOptions),
    ];
    World.add(engine.world, edgeList);
    const mouse = Mouse.create(render.canvas);
    const mouseConstraint = MouseConstraint.create(engine, {
      mouse: mouse,
      constraint: {
        stiffness: 1,
        render: {
          visible: false
        }
      }
    });
    World.add(engine.world, mouseConstraint);
    // 创建网
    var particleOptions = { friction: 0.0001, collisionFilter: { group: -1 }, render: { visible: false } };
    var constraintOptions = {
      stiffness: 0.8, render: {
        strokeStyle: "#ff0000",
      },
      collisionFilter: {
        group: -1 // 不与自身小球发生碰撞
      }
    };
    //   1: 软体物体的起始 X 坐标。
    //   2: 软体物体的起始 Y 坐标。
    //   3: 软体物体的列数，即横向的粒子数量。
    //   4: 软体物体的行数，即纵向的粒子数量。
    //   5: 横向间距。
    //   6: 纵向间距。
    //   7: 是否为链式连接（chain）。如果为 false，粒子之间将只有相邻的连接；如果为 true，米字连接
    //   8: 每个粒子的大小（半径）
    var columns = 20;
    var cloth = Composites.softBody(eleWidth / 2 - columns * 6, 0, columns, columns, 0, 0, false, 6, particleOptions, constraintOptions);
    /**将最上方的一排球进行固定*/
    for (var i = 0; i < columns; i++) {
      cloth.bodies[i].isStatic = true;
    }
    World.add(engine.world, cloth);

    let ball = Bodies.circle(100, 100, 50);
    World.add(engine.world, ball);
    Engine.run(engine);
    Render.run(render);
    /**创造两个桥墩————固定的两个竖向矩形代替*/
    var bridge_width = 30
    var bridge_height = 100
    var bridge_left = Bodies.rectangle(bridge_width / 2, eleHeight - bridge_height / 2, bridge_width, bridge_height, { isStatic: true });
    var bridge_right = Bodies.rectangle(eleWidth - bridge_width / 2, eleHeight - bridge_height / 2, bridge_width, bridge_height, { isStatic: true });

    /**桥上的木板————使用堆叠的一排矩形代替
     * stack(xx, yy, columns, rows, columnGap, rowGap, callback)
     * xx,yy 表示堆叠的混合体起点坐标；clumns 表示列数；rows 表示行数；columnGap表示列距；rowGap表示行距
     * callback中创建堆叠的物体——矩形
     * 注意：模板创建的位置要与桥墩配合好，否则铁索会被拉伸到变形
     */
    var stack = Composites.stack(bridge_width, eleHeight - bridge_height / 2, 6, 1, 10, 0, function (x, y) {
      return Bodies.rectangle(x, y, 60, 30, {
        chamfer: 15//chamfer 表示切角,这样矩形4个角会有15度的圆角
      })
    });

    /**
     * 木板创建好之后，现在需要使用 Composites.chain 链接起来，chain 用于将混合体中的刚体进行链接
     * chain(composite, xOffsetA, yOffsetA, xOffsetB, yOffsetB, options)
     * composite：被链接的混合体，如下所示为上面创建好的木板混合体
     * xOffsetA、yOffsetA 表示A物体链接点的偏移量，注意取值为百分比形式：(0,0)表示在物体中心、(0.5,0)表示表示物体右侧的中间位置、(0.5,-0.5)表示物体右上角
     * (-0.5,-0.5)表示物体左上角、(-0.5,0.5)表示物体左下角、其余以此类推。
     * xOffsetB, yOffsetB 则表示被链接的B物体，链接操作针对的是相邻物体，依次进行链接
     * 这里为桥上的木板创建"上、中、下"3条铁索进行链接。用铁索将模板连接起来
     */
    let chainOpt = {
      stiffness: 0.9,
      damping: 0.1
    }
    Composites.chain(stack, 0.4, -0.3, -0.4, -0.3, chainOpt);
    Composites.chain(stack, 0.4, 0, -0.4, 0, chainOpt);
    Composites.chain(stack, 0.4, 0.3, -0.4, 0.3, chainOpt);

    /**将已经链接好的木板左侧固定在左侧桥墩上
     * Constraint.create 创建约束———— 将两个物体通过约束点连接在一起，这个约束点就相当于一个钉子将两个物体固定在一起
     * 其参数是一个json对象,主要参数有：bodyA,pointA,bodyB,pointB,length,stiffness等
     * bodyA 和 bodyB 分别被连接的两个物体
     * pointA 和 pointB 表示约束点的位置，其值为向量，默认为{x=0,y:0}在物体中心位置。以物体中心为原点进行量相减(三角形法则)
     * bridge_left 为左侧桥墩，pointA: {x: 0, y: -100}表示自身约束点在中心点往上偏移100px的位置
     * stack.bodies[0]：stack为桥面木板混合体，.bodies为获取混合体中的刚体(数组),[0]取第一块木板
     * pointB: {x: -25, y: 0}：第一块木板上的约束点位置，木板宽度为60，(-25,0)表示中心点往左偏移25px
     */
    var nailLeft = Constraint.create({
      bodyA: bridge_left,
      pointA: { x: 0, y: bridge_height / -2 },
      bodyB: stack.bodies[0],
      pointB: { x: -25, y: 0 },
      length: bridge_width,
      stiffness: 0,
    });

    /**使用约束(可以看成一个钉子)将最后一块木板与右侧桥墩连接起来————与上面同理*/
    var nailRight = Constraint.create({
      bodyA: stack.bodies[stack.bodies.length - 1],
      pointA: { x: 25, y: 0 },
      bodyB: bridge_right,
      pointB: { x: 0, y: bridge_height / -2 },
      length: bridge_width,
      stiffness: 0,
    });

    var stack_circle = Composites.stack((eleWidth - 10 * 5) / 2, eleHeight / 2, 5, 5, 0, 0, function (x, y) {
      return Bodies.circle(x, y, 10);
    });
    World.add(engine.world, stack_circle);
    World.add(engine.world, [bridge_left, bridge_right, stack, nailLeft, nailRight]);
  </script>
</body>

</html>